---
sidebar_label: LDAP
description: >-
  The LDAP secret engine manages LDAP entry passwords.
---

# LDAP secrets engine

:::warning

**Note**: This engine can use external X.509 certificates as part of TLS or signature validation.
   Verifying signatures against X.509 certificates that use SHA-1 is deprecated and is no longer
   usable without a workaround. See the
   [deprecation FAQ](/docs/deprecation/faq#q-what-is-the-impact-of-removing-support-for-x-509-certificates-with-signatures-that-use-sha-1)
   for more information.

:::

The LDAP secrets engine provides management of LDAP credentials as well as dynamic
creation of credentials. It supports integration with implementations of the LDAP
v3 protocol, including OpenLDAP, Active Directory, and IBM Resource Access Control
Facility (RACF).

The secrets engine has three primary features:
- [Static Credentials](/docs/secrets/ldap#static-credentials)
- [Dynamic Credentials](/docs/secrets/ldap#dynamic-credentials)
- [Service Account Check-Out](/docs/secrets/ldap#service-account-check-out)

## Setup

1. Enable the LDAP secret engine:

   ```sh
   $ bao secrets enable ldap
   ```

   By default, the secrets engine will mount at the name of the engine. To
   enable the secrets engine at a different path, use the `-path` argument.

2. Configure the credentials that OpenBao uses to communicate with LDAP
   to generate passwords:

   ```sh
   $ bao write ldap/config \
       binddn=$USERNAME \
       bindpass=$PASSWORD \
       url=ldaps://138.91.247.105
   ```

   Note: it's recommended a dedicated entry management account be created specifically for OpenBao.

3. Rotate the root password so only OpenBao knows the credentials:

   ```sh
   $ bao write -f ldap/rotate-root
   ```

   Note: it's not possible to retrieve the generated password once rotated by OpenBao.
   It's recommended a dedicated entry management account be created specifically for OpenBao.

### Schemas

The LDAP Secret Engine supports three different schemas:

- `openldap` (default)
- `racf`
- `ad`

#### OpenLDAP

By default, the LDAP Secret Engine assumes the entry password is stored in `userPassword`.
There are many object classes that provide `userPassword` including for example:

- `organization`
- `organizationalUnit`
- `organizationalRole`
- `inetOrgPerson`
- `person`
- `posixAccount`

#### Resource access control facility (RACF)

For managing IBM's Resource Access Control Facility (RACF) security system, the secret
engine must be configured to use the schema `racf`.

Generated passwords must be 8 characters or less to support RACF. The length of the
password can be configured using a [password policy](/docs/concepts/password-policies):

```bash
$ bao write ldap/config \
	binddn=$USERNAME \
	bindpass=$PASSWORD \
	url=ldaps://138.91.247.105 \
	schema=racf \
	password_policy=racf_password_policy
```

#### Active directory (AD)

For managing Active Directory instances, the secret engine must be configured to use the
schema `ad`.

```bash
$ bao write ldap/config \
	binddn=$USERNAME \
	bindpass=$PASSWORD \
	url=ldaps://138.91.247.105 \
	schema=ad
```

## Static credentials

### Setup

1. Configure a static role that maps a name in OpenBao to an entry in LDAP.
   Password rotation settings will be managed by this role.

   ```sh
   $ bao write ldap/static-role/lf-edge\
       dn='uid=lf-edge,ou=users,dc=lf-edge,dc=com' \
       username='openbao'\
       rotation_period="24h"
   ```

2. Request credentials for the "openbao" role:

   ```sh
   $ bao read ldap/static-cred/lf-edge
   ```

### Password rotation

Passwords can be managed in two ways:

- automatic time based rotation
- manual rotation

### Auto password rotation

Passwords will automatically be rotated based on the `rotation_period` configured
in the static role (minimum of 5 seconds). When requesting credentials for a static
role, the response will include the time before the next rotation (`ttl`).

Auto-rotation is currently only supported for static roles. The `binddn` account used
by OpenBao should be rotated using the `rotate-root` endpoint to generate a password
only OpenBao will know.

### Manual rotation

Static roles can be manually rotated using the `rotate-role` endpoint. When manually
rotated the rotation period will start over.

### Deleting static roles

Passwords are not rotated upon deletion of a static role. The password should be manually
rotated prior to deleting the role or revoking access to the static role.

## Dynamic credentials

### Setup

Dynamic credentials can be configured by calling the `/role/:role_name` endpoint:

```bash
$ bao write ldap/role/dynamic-role \
  creation_ldif=@/path/to/creation.ldif \
  deletion_ldif=@/path/to/deletion.ldif \
  rollback_ldif=@/path/to/rollback.ldif \
  default_ttl=1h \
  max_ttl=24h
```

:::info

Note: The `rollback_ldif` argument is optional, but recommended. The statements within `rollback_ldif` will be
executed if the creation fails for any reason. This ensures any entities are removed in the event of a failure.

:::

To generate credentials:

```bash
$ bao read ldap/creds/dynamic-role
Key                    Value
---                    -----
lease_id               ldap/creds/dynamic-role/HFgd6uKaDomVMvJpYbn9q4q5
lease_duration         1h
lease_renewable        true
distinguished_names    [cn=v_token_dynamic-role_FfH2i1c4dO_1611952635,ou=users,dc=learn,dc=example]
password               xWMjkIFMerYttEbzfnBVZvhRQGmhpAA0yeTya8fdmDB3LXDzGrjNEPV2bCPE9CW6
username               v_token_testrole_FfH2i1c4dO_1611952635
```

The `distinguished_names` field is an array of DNs that are created from the `creation_ldif` statements. If more than
one LDIF entry is included, the DN from each statement will be included in this field. Each entry in this field
corresponds to a single LDIF statement. No de-duplication occurs and order is maintained.

### LDIF entries

User account management is provided through LDIF entries. The LDIF entries may be a base64-encoded version of the
LDIF string. The string will be parsed and validated to ensure that it adheres to LDIF syntax. A good reference
for proper LDIF syntax can be found [here](https://ldap.com/ldif-the-ldap-data-interchange-format/).

Some important things to remember when crafting your LDIF entries:

- There should not be any trailing spaces on any line, including empty lines
- Each `modify` block needs to be preceded with an empty line
- Multiple modifications for a `dn` can be defined in a single `modify` block. Each modification needs to close
  with a single dash (`-`)

### Active directory (AD)

For Active Directory, there are a few additional details that are important to remember:

To create a user programmatically in AD, you first `add` a user object and then `modify` that user to provide a
password and enable the account.

- Passwords in AD are set using the `unicodePwd` field. This must be proceeded by two (2) colons (`::`).
- When setting a password programmatically in AD, the following criteria must be met:

  - The password must be enclosed in double quotes (`" "`)
  - The password must be in [`UTF16LE` format](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2)
  - The password must be `base64`-encoded
  - Additional details can be found [here](https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/set-user-password-with-ldifde)

- Once a user's password has been set, it can be enabled. AD uses the `userAccountControl` field for this purpose:
  - To enable the account, set `userAccountControl` to `512`
  - You will likely also want to disable AD's password expiration for this dynamic user account. The
    `userAccountControl` value for this is: `65536`
  - `userAccountControl` flags are cumulative, so to set both of the above two flags, add up the two values
    (`512 + 65536 = 66048`): set `userAccountControl` to `66048`
  - See [here](https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties#property-flag-descriptions)
    for details on `userAccountControl` flags

`sAMAccountName` is a common field when working with AD users. It is used to provide compatibility with legacy
Windows NT systems and has a limit of 20 characters. Keep this in mind when defining your `username_template`.
See [here](https://docs.microsoft.com/en-us/windows/win32/adschema/a-samaccountname) for additional details.

Since the default `username_template` is longer than 20 characters which follows the template of `v_{{.DisplayName}}_{{.RoleName}}_{{random 10}}_{{unix_time}}`, we recommend customising the `username_template` on the role configuration to generate accounts with names less than 20 characters. Please refer to the [username templating document](/docs/concepts/username-templating) for more information.

With regard to adding dynamic users to groups, AD doesn't let you directly modify a user's `memberOf` attribute.
The `member` attribute of a group and `memberOf` attribute of a user are
[linked attributes](https://docs.microsoft.com/en-us/windows/win32/ad/linked-attributes). Linked attributes are
forward link/back link pairs, with the forward link able to be modified. In the case of AD group membership, the
group's `member` attribute is the forward link. In order to add a newly-created dynamic user to a group, we also
need to issue a `modify` request to the desired group and update the group membership with the new user.

#### Active directory LDIF example

The various `*_ldif` parameters are templates that use the [go template](https://golang.org/pkg/text/template/)
language. A complete LDIF example for creating an Active Directory user account is provided here for reference:

```ldif
dn: CN={{.Username}},OU=OpenBao,DC=adtesting,DC=lab
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
userPrincipalName: {{.Username}}@adtesting.lab
sAMAccountName: {{.Username}}

dn: CN={{.Username}},OU=OpenBao,DC=adtesting,DC=lab
changetype: modify
replace: unicodePwd
unicodePwd::{{ printf "%q" .Password | utf16le | base64 }}
-
replace: userAccountControl
userAccountControl: 66048
-

dn: CN=test-group,OU=OpenBao,DC=adtesting,DC=lab
changetype: modify
add: member
member: CN={{.Username}},OU=OpenBao,DC=adtesting,DC=lab
-
```

## Service account Check-Out

Service account check-out provides a library of service accounts that can be checked out
by a person or by machines. OpenBao will automatically rotate the password each time a
service account is checked in. Service accounts can be voluntarily checked in, or OpenBao
will check them in when their lending period (or, "ttl", in OpenBao's language) ends.

The service account check-out functionality works with various [schemas](/api-docs/secret/ldap#schema),
including OpenLDAP, Active Directory, and RACF. In the following usage example, the secrets
engine is configured to manage a library of service accounts in an Active Directory instance.

First we'll need to enable the LDAP secrets engine and tell it how to securely connect
to an AD server.

```shell-session
$ bao secrets enable ldap
Success! Enabled the ad secrets engine at: ldap/

$ bao write ldap/config \
    binddn=$USERNAME \
    bindpass=$PASSWORD \
    url=ldaps://138.91.247.105 \
    userdn='dc=example,dc=com'
```

Our next step is to designate a set of service accounts for check-out.

```shell-session
$ bao write ldap/library/accounting-team \
    service_account_names=fizz@example.com,buzz@example.com \
    ttl=10h \
    max_ttl=20h \
    disable_check_in_enforcement=false
```

In this example, the service account names of `fizz@example.com` and `buzz@example.com` have
already been created on the remote AD server. They've been set aside solely for OpenBao to handle.
The `ttl` is how long each check-out will last before OpenBao checks in a service account,
rotating its password during check-in. The `max_ttl` is the maximum amount of time it can live
if it's renewed. These default to `24h`, and both use [duration format strings](/docs/concepts/duration-format).
Also by default, a service account must be checked in by the same OpenBao entity or client token that
checked it out. However, if this behavior causes problems, set `disable_check_in_enforcement=true`.

When a library of service accounts has been created, view their status at any time to see if they're
available or checked out.

```shell-session
$ bao read ldap/library/accounting-team/status
Key                 Value
---                 -----
buzz@example.com    map[available:true]
fizz@example.com    map[available:true]
```

To check out any service account that's available, simply execute:

```shell-session
$ bao write -f ldap/library/accounting-team/check-out
Key                     Value
---                     -----
lease_id                ldap/library/accounting-team/check-out/EpuS8cX7uEsDzOwW9kkKOyGW
lease_duration          10h
lease_renewable         true
password                ?@09AZKh03hBORZPJcTDgLfntlHqxLy29tcQjPVThzuwWAx/Twx4a2ZcRQRqrZ1w
service_account_name    fizz@example.com
```

If the default `ttl` for the check-out is higher than needed, set the check-out to last
for a shorter time by using:

```shell-session
$ bao write ldap/library/accounting-team/check-out ttl=30m
Key                     Value
---                     -----
lease_id                ldap/library/accounting-team/check-out/gMonJ2jB6kYs6d3Vw37WFDCY
lease_duration          30m
lease_renewable         true
password                ?@09AZerLLuJfEMbRqP+3yfQYDSq6laP48TCJRBJaJu/kDKLsq9WxL9szVAvL/E1
service_account_name    buzz@example.com
```

This can be a nice way to say, "Although I _can_ have a check-out for 24 hours, if I
haven't checked it in after 30 minutes, I forgot or I'm a dead instance, so you can just
check it back in."

If no service accounts are available for check-out, OpenBao will return a 400 Bad Request.

```shell-session
$ bao write -f ldap/library/accounting-team/check-out
Error writing data to ldap/library/accounting-team/check-out: Error making API request.

URL: POST http://localhost:8200/v1/ldap/library/accounting-team/check-out
Code: 400. Errors:

* No service accounts available for check-out.
```

To extend a check-out, renew its lease.

```shell-session
$ bao lease renew ldap/library/accounting-team/check-out/0C2wmeaDmsToVFc0zDiX9cMq
Key                Value
---                -----
lease_id           ldap/library/accounting-team/check-out/0C2wmeaDmsToVFc0zDiX9cMq
lease_duration     10h
lease_renewable    true
```

Renewing a check-out means its current password will live longer, since passwords are rotated
anytime a password is _checked in_ either by a caller, or by OpenBao because the check-out `ttl`
ends.

To check a service account back in for others to use, call:

```shell-session
$ bao write -f ldap/library/accounting-team/check-in
Key          Value
---          -----
check_ins    [fizz@example.com]
```

Most of the time this will just work, but if multiple service accounts are checked out by the same
caller, OpenBao will need to know which one(s) to check in.

```shell-session
$ bao write ldap/library/accounting-team/check-in service_account_names=fizz@example.com
Key          Value
---          -----
check_ins    [fizz@example.com]
```

To perform a check-in, OpenBao verifies that the caller _should_ be able to check in a given service account.
To do this, OpenBao looks for either the same entity ID
used to check out the service account, or the same client token.

If a caller is unable to check in a service account, or simply doesn't try,
OpenBao will check it back in automatically when the `ttl` expires. However, if that is too long,
service accounts can be forcibly checked in by a highly privileged user through:

```shell-session
$ bao write -f ldap/library/manage/accounting-team/check-in
Key          Value
---          -----
check_ins    [fizz@example.com]
```

Or, alternatively, revoking the secret's lease has the same effect.

```shell-session
$ bao lease revoke ldap/library/accounting-team/check-out/PvBVG0m7pEg2940Cb3Jw3KpJ
All revocation operations queued successfully!
```

## LDAP password policy

The LDAP secret engine does not hash or encrypt passwords prior to modifying
values in LDAP. This behavior can cause plaintext passwords to be stored in LDAP.

To avoid having plaintext passwords stored, the LDAP server should be configured
with an LDAP password policy (ppolicy, not to be confused with an OpenBao password
policy). A ppolicy can enforce rules such as hashing plaintext passwords by default.

The following is an example of an LDAP password policy to enforce hashing on the
data information tree (DIT) `dc=openbao,dc=com`:

```
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: ppolicy

dn: olcOverlay={2}ppolicy,olcDatabase={1}mdb,cn=config
changetype: add
objectClass: olcPPolicyConfig
objectClass: olcOverlayConfig
olcOverlay: {2}ppolicy
olcPPolicyDefault: cn=default,ou=pwpolicies,dc=openbao,dc=com
olcPPolicyForwardUpdates: FALSE
olcPPolicyHashCleartext: TRUE
olcPPolicyUseLockout: TRUE
```

## API

The LDAP secrets engine has a full HTTP API. Please see the [LDAP secrets engine API docs](/api-docs/secret/ldap)
for more details.
